<template>
    <div class="box">
        <a-space>
            <a-upload :multiple="false" :show-upload-list="false" :customRequest="(obj: any) => handleUpload(obj.file)">
                <a-button type="primary" :loading="upLoading" :disabled="upLoading"
                    :icon="h(UploadOutlined)">上传</a-button>
            </a-upload>
            <a-button @click="download" :icon="h(DownloadOutlined)">下载</a-button>
        </a-space>
        <a-progress :percent="percentage" />
    </div>
</template>



<script setup lang="ts">
import { ref, h, computed } from "vue"
import SparkMD5 from "spark-md5"
import { UploadOutlined, DownloadOutlined } from '@ant-design/icons-vue'
import { message } from 'ant-design-vue'
import { downloadFile } from '@/utils/tool'

const upLoading = ref(false)
const CHUNK_SIZE = 1024 * 1024; // 1MB
const fileName = ref(""); // 文件名称
const fileHash = ref(""); // 文件hash
// 在createChunks函数后添加
const totalChunks = ref(0); // 总分片数
// 在uploadChunks函数中添加进度追踪
let uploadedChunks = ref(0); // 已上传分片数

const percentage = computed(() => {
    let num = 0
    if (uploadedChunks.value == 0) {
        num = 0
    } else {
        num = (uploadedChunks.value / totalChunks.value) * 100
    }

    return Number(num.toFixed(2))
})

// 创建文件分片
const createChunks = (file: File) => {
    let start = 0;
    const chunks = [];
    while (start < file.size) {
        chunks.push(file.slice(start, start + CHUNK_SIZE));
        start += CHUNK_SIZE;
    }
    totalChunks.value = chunks.length
    return chunks;
};

// 计算文件内容hash值
const calculateHash = (file: File): Promise<string> => {
    return new Promise((resolve) => {
        const fileReader = new FileReader();
        fileReader.readAsArrayBuffer(file);
        fileReader.onload = function (e) {
            const spark = new SparkMD5.ArrayBuffer();
            spark.append((e.target as FileReader).result as ArrayBuffer);
            resolve(spark.end());
        };
    });
};

// 控制请求并发
const concurRequest = (taskPool: Array<() => Promise<Response>>, max: number): Promise<Array<Response | unknown>> => {
    return new Promise((resolve) => {
        if (taskPool.length === 0) {
            resolve([]);
            return;
        }

        const results: Array<Response | unknown> = [];
        let index = 0;
        let count = 0;

        const request = async () => {
            if (index === taskPool.length) return;
            const i = index;
            const task = taskPool[index];
            index++;
            try {
                results[i] = await task();
                uploadedChunks.value++
            } catch (err) {
                results[i] = err;
            } finally {
                count++;
                if (count === taskPool.length) {
                    resolve(results);
                }
                request();
            }
        };

        const times = Math.min(max, taskPool.length);
        for (let i = 0; i < times; i++) {
            request();
        }
    });
};

// 合并分片请求(文件完成上传)
const mergeRequest = async () => {
    fetch('http://127.0.0.1:3000/merge', {
        method: 'POST',
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify({
            fileHash: fileHash.value,
            fileName: fileName.value,
        })
    })
};

// 上传文件分片
const uploadChunks = async (chunks: Array<Blob>, existChunks: Array<string>) => {
    const formDatas = chunks.map((chunk, index) => ({
        fileHash: fileHash.value,
        chunkHash: fileHash.value + "-" + index,
        chunk,
    })).filter((item) => !existChunks.includes(item.chunkHash)).map((item) => {
        const formData = new FormData();
        formData.append("fileHash", item.fileHash);
        formData.append("chunkHash", item.chunkHash);
        formData.append("chunk", item.chunk);
        return formData;
    });
    const taskPool = formDatas.map((formData) => () =>
        fetch("http://127.0.0.1:3000/upload", {
            method: "POST",
            body: formData,
        })
    );

    // 控制请求并发
    await concurRequest(taskPool, 6);
    upLoading.value = false
    message.success('上传成功！')
    // 合并分片请求
    mergeRequest();
};

// 校验文件、文件分片是否存在
const verify = (fileHash: string, fileName: string) => {
    return fetch("http://127.0.0.1:3000/verify", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify({
            fileHash,
            fileName,
        }),
    }).then((res) => res.json());
};

// 绑定上传事件
const handleUpload = async (obj: File) => {
    upLoading.value = true
    const file: File = obj
    if (!file) return;

    fileName.value = file.name;

    // 创建文件分片
    const chunks = createChunks(file);

    // 计算文件内容hash值
    fileHash.value = await calculateHash(file);

    // 校验文件、文件分片是否存在
    const verifyRes = await verify(fileHash.value, fileName.value);
    const { existFile, existChunks } = verifyRes.data;
    if (existFile) {
        upLoading.value = false
        message.success('文件上传成功！')
        return
    }

    // 上传文件分片
    uploadChunks(chunks, existChunks);
};

const download = () => {
    const obj = {
        fileName: 'test.mp4'
    }
    downloadFile('http://127.0.0.1:3000/download/df6ed4bbc93613c68c8525e21bbddf98', obj)
}
</script>

<style lang="scss" scoped></style>
